﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Principal;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Netfraction.Common.DataStorage;
using Netfraction.Common.Logging;

namespace NetfractionHub.Security
{
	public class RoleManager
	{
		private static SqlDataStore _roleStore;
		private List<Role> _roles;
		private static bool _isPopulated;
		internal const string ROLES_DATABASE_NAME = "roles.db";
		internal const string ROLES_TABLE_NAME = "Roles";
		internal const string ROLE_PERMISSIONS_TABLE_NAME = "RolePermissions";

		private RoleManager()
		{
			_roles = new List<Role>();
			_isPopulated = false;
		}

		private static bool CheckStoreExists()
		{
			_roleStore = new SqlDataStore(SecurityDataSource.Source, RoleManager.ROLES_DATABASE_NAME);

			return _roleStore != null;
		}

		private static bool CheckStoreIsPopulated()
		{
			if (!CheckStoreExists())
				return false;

			if (!_roleStore.TablesExist(new string[] { ROLES_TABLE_NAME, ROLE_PERMISSIONS_TABLE_NAME }))
				PopulateRolesStore();

			return true;
		}

		#region Singleton Pattern
		// Use singleton pattern.
		private static RoleManager _instance;

		/// <summary>
		/// Obtain singleton instance of RoleManager
		/// </summary>
		/// <returns></returns>
		public static RoleManager GetInstance()
		{
			// Check instance
			if (RoleManager._instance == null)
			{
				// Initialise instance
				RoleManager._instance = new RoleManager();
			}

			// Return instance
			return RoleManager._instance;
		}
		#endregion Singleton Pattern

		private static Dictionary<int, Dictionary<Type, AccessState>> rolePermissionCache = null;
		private static Dictionary<int, AccessPermissionBase> accessPermissionCache = null;

		public static void LoadRoles()
		{
			_isPopulated = CheckStoreIsPopulated();
			if (!_isPopulated)
				return;

			LogManager.WriteEntry("Loading Roles from: " + _roleStore.FullPath, LogEventId.LoadingRoles, LogLevel.Basic);



			Role newRole = null;
			using (Community.CsharpSqlite.SQLiteClient.SqliteDataReader sdr = _roleStore.ExecuteQuery("SELECT RoleId, Name, Ancestor FROM Roles"))
			{
				if (sdr != null)
				{
					while (sdr.Read())
					{
						newRole = Role.LoadRole(sdr);
						if (newRole != null)
							_instance._roles.Add(newRole);
					}
				}
			}
		}

		private static void PopulateRolesStore()
		{
			if (!CheckStoreExists())
				_roleStore = new SqlDataStore(SecurityDataSource.Source, ROLES_DATABASE_NAME);

			if (!_roleStore.TableExists(ROLES_TABLE_NAME))
			{
				// IF NOT EXISTS is redundant. We wrap this block in the if-statement to ensure these types always exists.
				_roleStore.ExecuteNonQuery(string.Format("CREATE TABLE IF NOT EXISTS {0} (RoleId INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT, Ancestor INTEGER)", ROLES_TABLE_NAME));
				Dictionary<string, int> defaultRoles = new Dictionary<string, int>();
				defaultRoles.Add("Owner", 1);
				defaultRoles.Add("Administrator", 1);
				defaultRoles.Add("Operator", 2);
				defaultRoles.Add("VIP", 3);
				defaultRoles.Add("Registered User", 3);
				defaultRoles.Add("Normal User", 3);
				defaultRoles.Add("Limited User", 3);
				foreach (KeyValuePair<string, int> defRole in defaultRoles)
				{
					_roleStore.ExecuteNonQuery(String.Format(System.Globalization.CultureInfo.InvariantCulture, "INSERT INTO Roles (Name, Ancestor) VALUES ('{0}', '{1}')", defRole.Key, defRole.Value));
				}
			}
			if (!_roleStore.TableExists(ROLE_PERMISSIONS_TABLE_NAME))
				_roleStore.ExecuteNonQuery(string.Format("CREATE TABLE IF NOT EXISTS {0} (RoleId INTEGER, PermissionId INTEGER, AccessState INTEGER)", ROLE_PERMISSIONS_TABLE_NAME));

			LogManager.WriteEntry("Creating Roles", LogEventId.CreatingRoles, LogLevel.Detailed);

			int curRoleId = GetRoleIdByName("Owner");
			SetRoleAllowPermission(_roleStore, curRoleId, new List<Type>(new[] {
				typeof(Permissions.GetMessageOfTheDayPermission), typeof(Permissions.SetMessageOfTheDayPermission), 
				typeof(Permissions.SetHubNamePermission), typeof(Permissions.RestartHubPermission), 
				typeof(Permissions.QuitHubPermission), typeof(Permissions.AddConnectionListenerPermission), 
				typeof(Permissions.RemoveConnectionListenerPermission), typeof(Permissions.GetConfigurationVariablePermission), 
				typeof(Permissions.SetConfigurationVariablePermission), typeof(Permissions.ListConfigurationPermission), 
				typeof(Permissions.ReloadConfigurationPermission), typeof(Permissions.OverrideMaximumChatLinesPermission), 
				typeof(Permissions.OverrideMaximumChatLengthPermission), typeof(Permissions.BanClientIdPermission), 
				typeof(Permissions.BanIpPermission), typeof(Permissions.TemporaryBanClientIdPermission), 
				typeof(Permissions.TemporaryBanIpPermission), typeof(Permissions.ListBansPermission), 
				typeof(Permissions.BanNicknamePermission), typeof(Permissions.BanDescriptionPermission), 
				typeof(Permissions.BanEmailPermission), typeof(Permissions.ListInfoBansPermission), 
				typeof(Permissions.BanChatMessagePermission), typeof(Permissions.ListChatBansPermission), 
				typeof(Permissions.ShowIpPermission), typeof(Permissions.HideIpPermission), 
				typeof(Permissions.GetOwnIpPermission), typeof(Permissions.GetIdentityIpPermission), 
				typeof(Permissions.GetOwnClientIdPermission), typeof(Permissions.GetIdentityClientIdPermission), 
				typeof(Permissions.KickIdentityPermission), typeof(Permissions.DropIdentityPermission), 
				typeof(Permissions.ShowIdentityPermission), typeof(Permissions.HideIdentityPermission), 
				typeof(Permissions.ShowSelfPermission), typeof(Permissions.HideSelfPermission), 
				typeof(Permissions.RegisterUserPermission), typeof(Permissions.UpdateUserPermission), 
				typeof(Permissions.UnregisterUserPermission), typeof(Permissions.ChangePasswordPermission), 
				typeof(Permissions.SetRolePermission), typeof(Permissions.GrantPermissionPermission), 
				typeof(Permissions.DenyPermissionPermission), typeof(Permissions.ListIdentityPermissionsPermission), 
				typeof(Permissions.ListOwnIdentityPermissionsPermission), typeof(Permissions.ListAllPermissionsPermission), 
				typeof(Permissions.CheckUptimePermission), typeof(Permissions.PublicChatPermission), 
				typeof(Permissions.PrivateChatPermission), typeof(Permissions.PrivateMessageAllClientsPermission), 
				typeof(Permissions.SharePermission), typeof(Permissions.DownloadPermission), 
				typeof(Permissions.SearchPermission), typeof(Permissions.LoadPluginPermission),
				typeof(Permissions.RemovePluginPermission), typeof(Permissions.AddBotPermission), 
				typeof(Permissions.RemoveBotPermission)
			}));

			curRoleId = GetRoleIdByName("Administrator");
			SetRoleAllowPermission(_roleStore, curRoleId, new List<Type>(new[] {
				typeof(Permissions.GetMessageOfTheDayPermission), typeof(Permissions.SetMessageOfTheDayPermission), 
				typeof(Permissions.SetHubNamePermission), typeof(Permissions.GetConfigurationVariablePermission), 
				typeof(Permissions.SetConfigurationVariablePermission), typeof(Permissions.ListConfigurationPermission), 
				typeof(Permissions.ReloadConfigurationPermission), typeof(Permissions.OverrideMaximumChatLinesPermission), 
				typeof(Permissions.OverrideMaximumChatLengthPermission), typeof(Permissions.BanClientIdPermission), 
				typeof(Permissions.BanIpPermission), typeof(Permissions.TemporaryBanClientIdPermission), 
				typeof(Permissions.TemporaryBanIpPermission), typeof(Permissions.ListBansPermission), 
				typeof(Permissions.BanNicknamePermission), typeof(Permissions.BanDescriptionPermission), 
				typeof(Permissions.BanEmailPermission), typeof(Permissions.ListInfoBansPermission), 
				typeof(Permissions.BanChatMessagePermission), typeof(Permissions.ListChatBansPermission), 
				typeof(Permissions.ShowIpPermission), typeof(Permissions.HideIpPermission), 
				typeof(Permissions.GetOwnIpPermission), typeof(Permissions.GetIdentityIpPermission), 
				typeof(Permissions.GetOwnClientIdPermission), typeof(Permissions.GetIdentityClientIdPermission), 
				typeof(Permissions.KickIdentityPermission), typeof(Permissions.DropIdentityPermission), 
				typeof(Permissions.ShowIdentityPermission), typeof(Permissions.HideIdentityPermission), 
				typeof(Permissions.ShowSelfPermission), typeof(Permissions.HideSelfPermission), 
				typeof(Permissions.RegisterUserPermission), typeof(Permissions.UpdateUserPermission), 
				typeof(Permissions.UnregisterUserPermission), typeof(Permissions.ChangePasswordPermission), 
				typeof(Permissions.SetRolePermission), typeof(Permissions.GrantPermissionPermission), 
				typeof(Permissions.DenyPermissionPermission), typeof(Permissions.ListIdentityPermissionsPermission), 
				typeof(Permissions.ListOwnIdentityPermissionsPermission), typeof(Permissions.ListAllPermissionsPermission), 
				typeof(Permissions.CheckUptimePermission), typeof(Permissions.PublicChatPermission), 
				typeof(Permissions.PrivateChatPermission), typeof(Permissions.PrivateMessageAllClientsPermission), 
				typeof(Permissions.SharePermission), typeof(Permissions.DownloadPermission), 
				typeof(Permissions.SearchPermission), typeof(Permissions.LoadPluginPermission), 
				typeof(Permissions.RemovePluginPermission), typeof(Permissions.AddBotPermission), typeof(Permissions.RemoveBotPermission)
			}));

			curRoleId = GetRoleIdByName("Operator");
			SetRoleAllowPermission(_roleStore, curRoleId, new List<Type>(new[] {
				typeof(Permissions.GetMessageOfTheDayPermission), typeof(Permissions.SetMessageOfTheDayPermission), 
				typeof(Permissions.SetHubNamePermission), typeof(Permissions.GetConfigurationVariablePermission), 
				typeof(Permissions.SetConfigurationVariablePermission), typeof(Permissions.ListConfigurationPermission), 
				typeof(Permissions.ReloadConfigurationPermission), typeof(Permissions.OverrideMaximumChatLinesPermission), 
				typeof(Permissions.OverrideMaximumChatLengthPermission), typeof(Permissions.BanClientIdPermission), 
				typeof(Permissions.BanIpPermission), typeof(Permissions.TemporaryBanClientIdPermission), 
				typeof(Permissions.TemporaryBanIpPermission), typeof(Permissions.ListBansPermission), 
				typeof(Permissions.BanNicknamePermission), typeof(Permissions.BanDescriptionPermission), 
				typeof(Permissions.BanEmailPermission), typeof(Permissions.ListInfoBansPermission), 
				typeof(Permissions.BanChatMessagePermission), typeof(Permissions.ListChatBansPermission), 
				typeof(Permissions.ShowIpPermission), typeof(Permissions.HideIpPermission), 
				typeof(Permissions.GetOwnIpPermission), typeof(Permissions.GetIdentityIpPermission), 
				typeof(Permissions.GetOwnClientIdPermission), typeof(Permissions.GetIdentityClientIdPermission), 
				typeof(Permissions.KickIdentityPermission), typeof(Permissions.DropIdentityPermission), 
				typeof(Permissions.RegisterUserPermission), typeof(Permissions.UpdateUserPermission), 
				typeof(Permissions.UnregisterUserPermission), typeof(Permissions.ChangePasswordPermission), 
				typeof(Permissions.SetRolePermission), typeof(Permissions.GrantPermissionPermission), 
				typeof(Permissions.DenyPermissionPermission), typeof(Permissions.ListIdentityPermissionsPermission), 
				typeof(Permissions.ListOwnIdentityPermissionsPermission), typeof(Permissions.ListAllPermissionsPermission), 
				typeof(Permissions.CheckUptimePermission), typeof(Permissions.PublicChatPermission), 
				typeof(Permissions.PrivateChatPermission), typeof(Permissions.PrivateMessageAllClientsPermission), 
				typeof(Permissions.SharePermission), typeof(Permissions.DownloadPermission), typeof(Permissions.SearchPermission)
			}));

			curRoleId = GetRoleIdByName("VIP");
			SetRoleAllowPermission(_roleStore, curRoleId, new List<Type>(new[] {
				typeof(Permissions.GetMessageOfTheDayPermission), typeof(Permissions.GetOwnIpPermission), 
				typeof(Permissions.ChangePasswordPermission), typeof(Permissions.ListOwnIdentityPermissionsPermission), 
				typeof(Permissions.CheckUptimePermission), typeof(Permissions.PublicChatPermission), 
				typeof(Permissions.PrivateChatPermission), typeof(Permissions.SharePermission), 
				typeof(Permissions.DownloadPermission), typeof(Permissions.SearchPermission)
			}));

			curRoleId = GetRoleIdByName("Registered User");
			SetRoleAllowPermission(_roleStore, curRoleId, new List<Type>(new[] {
				typeof(Permissions.GetMessageOfTheDayPermission), typeof(Permissions.GetOwnIpPermission), 
				typeof(Permissions.ChangePasswordPermission), typeof(Permissions.ListOwnIdentityPermissionsPermission), 
				typeof(Permissions.CheckUptimePermission), typeof(Permissions.PublicChatPermission), 
				typeof(Permissions.PrivateChatPermission), typeof(Permissions.SharePermission), 
				typeof(Permissions.DownloadPermission), typeof(Permissions.SearchPermission)
			}));

			curRoleId = GetRoleIdByName("Normal User");
			SetRoleAllowPermission(_roleStore, curRoleId, new List<Type>(new[] {
				typeof(Permissions.GetMessageOfTheDayPermission), typeof(Permissions.GetOwnIpPermission), 
				typeof(Permissions.ListOwnIdentityPermissionsPermission), typeof(Permissions.CheckUptimePermission), 
				typeof(Permissions.PublicChatPermission), typeof(Permissions.PrivateChatPermission), 
				typeof(Permissions.SharePermission), typeof(Permissions.DownloadPermission), typeof(Permissions.SearchPermission)
			}));

			curRoleId = GetRoleIdByName("Limited User");
			SetRoleAllowPermission(_roleStore, curRoleId, new List<Type>(new[] {
				typeof(Permissions.GetOwnIpPermission), typeof(Permissions.ListOwnIdentityPermissionsPermission), 
				typeof(Permissions.PublicChatPermission), typeof(Permissions.PrivateChatPermission)
			}));
		}

		private static void SetRoleAllowPermission(SqlDataStore _sqlStore, int roleId, List<Type> permissionTypes)
		{
			foreach (Type permType in permissionTypes)
			{
				SetRolePermission(_sqlStore, roleId, permType, AccessState.Allow);
			}
		}

		private static void SetRoleDenyPermission(SqlDataStore _sqlStore, int roleId, List<Type> permissionTypes)
		{
			foreach (Type permType in permissionTypes)
			{
				SetRolePermission(_sqlStore, roleId, permType, AccessState.Deny);
			}
		}

		private static void SetRoleAllowPermission(SqlDataStore _sqlStore, int roleId, Type permissionType)
		{
			SetRolePermission(_sqlStore, roleId, permissionType, AccessState.Allow);
		}

		private static void SetRoleDenyPermission(SqlDataStore _sqlStore, int roleId, Type permissionType)
		{
			SetRolePermission(_sqlStore, roleId, permissionType, AccessState.Deny);
		}

		private static void SetRolePermission(SqlDataStore _sqlStore, int roleId, List<Type> permissionTypes, AccessState state)
		{
			foreach (Type permType in permissionTypes)
			{
				SetRolePermission(_sqlStore, roleId, permType, state);
			}
		}

		private static void SetRolePermission(SqlDataStore _sqlStore, int roleId, Type permissionType, AccessState state)
		{
			try
			{
				int permissionId = AccessPermissionBase.GetPermissionId(permissionType);
				if (permissionId > 0)
					SetRolePermissionInternal(_sqlStore, roleId, permissionId, state);
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
			}
		}

		private static int SetRolePermissionInternal(SqlDataStore _sqlStore, int roleId, int permissionId, AccessState state)
		{
			// check if '' should be used around INTEGER values
			object retVal = _sqlStore.ExecuteScalar(String.Format(System.Globalization.CultureInfo.InvariantCulture,
				"SELECT count() FROM {0} WHERE RoleId={1} AND PermissionId={2}",
				ROLE_PERMISSIONS_TABLE_NAME,
				roleId,
				permissionId));

			if (retVal != null && (retVal is Int64 || retVal is Int32))
			{
				if ((retVal is Int64 ? SqlDataStore.CastInt64ToInt((Int64)retVal) : (int)retVal) == 0)
				{
					// use INSERT
					return _sqlStore.ExecuteNonQuery(String.Format(System.Globalization.CultureInfo.InvariantCulture,
						"INSERT INTO {0} (RoleId, PermissionId, AccessState) VALUES ({1}, {2}, {3})",
						ROLE_PERMISSIONS_TABLE_NAME,
						roleId,
						permissionId,
						(int)state));
				}
				else
				{
					// use UPDATE
					return _sqlStore.ExecuteNonQuery(String.Format(System.Globalization.CultureInfo.InvariantCulture,
						"UPDATE {0} SET AccessState={1} WHERE RoleId={2} AND PermissionId={3})",
						ROLE_PERMISSIONS_TABLE_NAME,
						(int)state,
						roleId,
						permissionId));
				}
			}

			return 0;
		}

		private static int GetRoleIdByName(string roleName)
		{
			object retVal = _roleStore.ExecuteScalar(String.Format(System.Globalization.CultureInfo.InvariantCulture,
			   "SELECT RoleId FROM {0} WHERE Name='{1}'",
			   ROLES_TABLE_NAME,
			   roleName));

			if (retVal != null && (retVal is Int64 || retVal is Int32))
				return retVal is Int64 ? SqlDataStore.CastInt64ToInt((Int64)retVal) : (int)retVal;
			else
				return 0;
		}

		public static bool LoadRolePermissions()
		{
			if (!_isPopulated)
				return false;

			accessPermissionCache = AccessPermissionBase.LoadPermissions();
			rolePermissionCache = new Dictionary<int,Dictionary<Type,AccessState>>();
			using (Community.CsharpSqlite.SQLiteClient.SqliteDataReader sdr = _roleStore.ExecuteQuery(
				string.Format("SELECT PermissionId, AccessState, RoleId FROM {0} ORDER BY RoleId", ROLE_PERMISSIONS_TABLE_NAME)))
			{
				if (sdr != null)
				{
					while (sdr.Read())
					{
						int roleId = sdr.GetInt32(2);
						if (rolePermissionCache.ContainsKey(roleId))
							rolePermissionCache[roleId].Add(accessPermissionCache[sdr.GetInt32(0)].GetType(), (AccessState)sdr.GetInt32(1));
						else
							rolePermissionCache.Add(roleId, new Dictionary<Type, AccessState> 
								{ 
									{ accessPermissionCache[sdr.GetInt32(0)].GetType(), (AccessState)sdr.GetInt32(1) } 
								});
					}
				}
			}

			return true;
		}

		public static Dictionary<Type, AccessState> GetRolePermissionsByRoleId(int roleId)
		{
			if (rolePermissionCache == null && !LoadRolePermissions())
				return null;

			return rolePermissionCache.ContainsKey(roleId) ? rolePermissionCache[roleId] : new Dictionary<Type, AccessState>();
		}

		public static Role GetRoleById(byte id)
		{
			var roles = _instance._roles.Where(p => p.RoleId == id);
			return roles.Count() == 1 ? roles.First() as Role : null;
		}

		public static Role GetRoleByName(string roleName)
		{
			var roles = _instance._roles.Where(p => p.Name == roleName);
			return roles.Count() == 1 ? roles.First() as Role : null;
		}

		/// <summary>
		/// Checks if roleX is an ancestor of roleY
		/// </summary>
		/// <param name="roleX"></param>
		/// <param name="roleY"></param>
		/// <returns></returns>
		public static bool IsInAncestorTree(Role roleX, Role roleY)
		{
			bool endLoop = false;
			if (roleY.Ancestor == null)
				return false;

			Role curAncestor = roleY.Ancestor;

			while (!endLoop)
			{
				if (curAncestor == roleX)
					return true;
				else
					if (curAncestor.Ancestor != null && curAncestor.RoleId != curAncestor.Ancestor.RoleId)
						curAncestor = curAncestor.Ancestor;
					else
						endLoop = true;
			}
			return false;
		}
	}
}
